﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Core.Extensions
{
	using System;
	using System.Globalization;

	public static class TypeExtensions
	{
		/// <summary>
		/// Gets the type of the property.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static Type GetPropertyType(this string name, object value)
		{
			var type = value.GetType();

			if (name.Contains("."))
			{
				var properties = name.Split(new char[] { '.' }, 2);
				var info = type.GetProperty(properties[0]);

				return properties[1].GetPropertyType(info.GetValue(value, null));
			}

			return type.GetProperty(name).PropertyType;
		}

		/// <summary>
		/// Gets the name of the property.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="value">The value.</param>
		/// <param name="property">The property.</param>
		/// <returns></returns>
		public static string GetPropertyName<T>(this object value, string property)
		{
			return typeof(T).GetProperty(property).Name;
		}

		/// <summary>
		/// Resolves the property.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static string ResolveProperty(this string name, object value)
		{
			if (value != null)
			{
				if (name.IndexOf('.') > -1)
				{
					var properties = name.Split(new char[] { '.' }, 2);
					var type = value.GetType();
					var info = type.GetProperty(properties[0]);

					return properties[1].ResolveProperty(info.GetValue(value, null));
				}
				else
				{
					var type = value.GetType();
					var info = type.GetProperty(name);
					var propertyValue = info.GetValue(value, null);

					if (info.PropertyType.IsEnum)
					{
						if (propertyValue == null)
						{
							return string.Empty;
						}

						var enumValue = string.Empty;

						if (info.PropertyType.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0)
						{
							var enumNames = Enum.GetNames(info.PropertyType);
							var enumValues = Enum.GetValues(info.PropertyType).As<int[]>();
							var enumIntValue = Convert.ToInt32(propertyValue, CultureInfo.InvariantCulture);

							if (enumIntValue == 0)
							{
								enumValue = enumNames[0];
							}
							else
							{
								for (var i = 0; i < enumValues.Length; i++)
								{
									if ((enumValues[i] & enumIntValue) != 0)
									{
										enumValue += string.Concat(enumNames[i], " ");
									}
								}
							}
						}
						else
						{
							enumValue = Enum.GetName(info.PropertyType, propertyValue);
						}

						return enumValue;
					}

					return (propertyValue == null) ? string.Empty : propertyValue.ToString();
				}
			}

			return string.Empty;
		}

		/// <summary>
		/// Gets the property.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static object GetProperty(this string name, object value)
		{
			if (value != null)
			{
				var type = value.GetType();

				if (name.IndexOf('.') > -1)
				{
					var properties = name.Split(new char[] { '.' }, 2);
					var info = type.GetProperty(properties[0]);

					return properties[1].GetProperty(info.GetValue(value, null));
				}

				return type.GetProperty(name).GetValue(value, null);
			}

			return null;
		}

		/// <summary>
		/// Sets the property.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <param name="value">The value.</param>
		/// <param name="propertyValue">The property value.</param>
		public static void SetProperty(this string name, object value, object propertyValue)
		{
			if (value == null)
			{
				return;
			}

			var type = value.GetType();

			if (name.IndexOf('.') > -1)
			{
				var properties = name.Split(new char[] { '.' }, 2);
				var info = type.GetProperty(properties[0]);
				var propValue = info.GetValue(value, null);

				properties[1].SetProperty(propValue, propertyValue);
			}
			else
			{

				var info = type.GetProperty(name);
				var propertyType = info.PropertyType;

				if (propertyValue == null && propertyType == typeof(bool))
				{
					propertyValue = false.ToString();
				}

				if (propertyValue == null)
				{
					throw new ArgumentNullException("propertyValue");
				}

				object propertyValueObject = null;

				if (propertyType.Name == "Nullable`1")
				{
					var genericTypes = propertyType.GetGenericArguments();
					var nullableType = genericTypes[0];
					var convertedValue = Convert.ChangeType(propertyValue, nullableType, CultureInfo.InvariantCulture);

					propertyValueObject = Activator.CreateInstance(propertyType, convertedValue);
				}
				else if (propertyType.IsEnum)
				{
					propertyValueObject = Convert.ChangeType(propertyValue, typeof(int), CultureInfo.InvariantCulture);
				}
				else
				{
					if (propertyValue is Guid)
					{
						propertyValueObject = Convert.ChangeType(new Guid(propertyValue.ToString()), propertyType, CultureInfo.InvariantCulture);
					}
					else
					{
						propertyValueObject = Convert.ChangeType(propertyValue, propertyType, CultureInfo.InvariantCulture);
					}
				}

				info.SetValue(value, propertyValueObject, null);
			}
		}

		/// <summary>
		/// Creates a instance of the specified type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		public static object CreateInstance(this Type type)
		{
			return Activator.CreateInstance(type);
		}
	}
}